package cn.blnp.net.flowable.event.end.cancel;

import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElementsContainer;
import org.flowable.bpmn.model.SubProcess;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

/**
 * <h3>结束事件-取消结束事件</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/8 13:52
 */
@Slf4j
@SpringBootTest
public class CancelEventEnd {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;

    /**
     *  <p>
     *      取消结束事件只能在事务子流程中使用，用于取消一个事务子流程的执行。在实际应用中，它通常会与取消事件、事务子流程和补偿事件搭配使用。当
     *  事务子流程流转到取消结束事件时，会抛出取消事件，取消事件会被依附在事务子流程上的取消边界事件捕获。取消边界事件会取消事务，并触发补偿机制。
     *  </p>
     *  <p>
     *      需要注意的是，在BPMN 2.0中，对于已经完成的活动，可以触发补偿机制；对于一些正在进行的活动，则不能触发补偿机制，只能触发取消机制。取消事件
     *  一定要包含补偿事件，否则无法运行，会抛出：{@code org.flowable.common.engine.api.FlowableException: No execution found for sub process of boundary cancel event xxxx}
     *  的异常。标签定义格式如下所示：
     *  </p>
     *  <pre>
     *      {@code
     *          <!-- 定义结束节点 -->
     *          <endEvent id="cancelEndEvent">
     *              <!-- 包含cancelEventDefinition子元素，代表取消结束事件 -->
     *              <cancelEventDefinition/>
     *          </endEvent>
     *      }
     *  </pre>
     *
     *  <h2>演示案例</h2>
     *  <hr/>
     *  <p>
     *      该流程为系统上线流程，启动后进入“系统上线事务子流程”，首先流转到“人工上线”用户任务节点，任务处理完成后流转到取消结束事件，抛出取消事件，触发“自动回滚”补偿
     *  机制，触发取消边界事件并结束子流程，进而流转到“问题排查”用户任务节点。该示例流程涉及取消边界事件、用户任务节点、服务任务节点和事务子流程，这些都是BPMN定义的流程元素.
     *  </p>
     *  <p>
     *      <b>流程定义文件：</b>{@code flow/cancelEventEnd.bpmn20.xml}<br/>
     *      <b>流程预览图：</b><br/>
     *      <img src="https://free4.yunpng.top/2025/05/08/681c5af1c9d1b.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("取消结束事件案例")
                .addClasspathResource("flow/cancelEventEnd.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        //启动流程
        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("cancelEndEventProcess");
        log.info("启动流程实例：{}", processInstance.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(processInstance.getId());
        //查询子流程第一个任务
        Task firstTaskOfSubProcess = taskQuery.singleResult();
        log.info("发起流程后，当前流程所在用户任务为：{}", firstTaskOfSubProcess.getName());
        //查询BpmnModel
        BpmnModel bpmnModel = repositoryService
                .getBpmnModel(processInstance.getProcessDefinitionId());
        //查询当前用户任务所在的父容器
        FlowElementsContainer firstTaskOfSubProcessContainer = bpmnModel
                .getFlowElement(firstTaskOfSubProcess.getTaskDefinitionKey())
                .getParentContainer();
        log.info("用户任务{}处于{}中", firstTaskOfSubProcess.getName(),
                firstTaskOfSubProcessContainer instanceof SubProcess ? "子流程" : "主流程");
        //完成子流程第一个任务
        taskService.complete(firstTaskOfSubProcess.getId());
        //查询主流程第一个任务
        Task firstTaskOfMainProcess = taskQuery.singleResult();
        log.info("完成第一个用户任务后，当前流程所在用户任务为：{}", firstTaskOfMainProcess.getName());
        //查询当前用户任务所在的父容器
        FlowElementsContainer firstTaskOfMainProcessContainer = bpmnModel
                .getFlowElement(firstTaskOfMainProcess.getTaskDefinitionKey())
                .getParentContainer();
        log.info("用户任务{}处于{}中", firstTaskOfMainProcess.getName(),
                firstTaskOfMainProcessContainer instanceof SubProcess ? "子流程" : "主流程");
        //完成子流程第一个任务
        taskService.complete(firstTaskOfMainProcess.getId());

        /**
         *  演示结果：
         * 2025-05-08 15:28:15.494  INFO 95712 --- [           main] c.b.n.f.event.end.cancel.CancelEventEnd  : 部署流程定义：01234cb9-2bde-11f0-92b7-50ebf6ec8f6d
         * 2025-05-08 15:28:15.572  INFO 95712 --- [           main] c.b.n.f.event.end.cancel.CancelEventEnd  : 启动流程实例：016b2b2d-2bde-11f0-92b7-50ebf6ec8f6d
         * 2025-05-08 15:28:15.584  INFO 95712 --- [           main] c.b.n.f.event.end.cancel.CancelEventEnd  : 发起流程后，当前流程所在用户任务为：人工上线
         * 2025-05-08 15:28:15.585  INFO 95712 --- [           main] c.b.n.f.event.end.cancel.CancelEventEnd  : 用户任务人工上线处于子流程中
         * 2025-05-08 15:28:15.614  INFO 95712 --- [           main] .c.CompensationForCancelEndEventDelegate : 补偿节点，执行自动回滚处理！
         * 2025-05-08 15:28:15.650  INFO 95712 --- [           main] c.b.n.f.event.end.cancel.CancelEventEnd  : 完成第一个用户任务后，当前流程所在用户任务为：问题排查
         * 2025-05-08 15:28:15.650  INFO 95712 --- [           main] c.b.n.f.event.end.cancel.CancelEventEnd  : 用户任务问题排查处于主流程中
         **/
    }
}
